package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/hyperledger/fabric-contract-api-go/contractapi"
	"strconv"
	"time"
)

type DataValue struct {
	Weight [][]float64 `json:"layer.weight"`
	Bias   []float64   `json:"layer.bias"`
}



//ModelKey will act as key in key-value pairs,and in this contract we temporarily assume that the value in key-value pairs is an int variable(while in reality it may be a structure or json)
type ModelKey struct {
	IsLocalModel bool	//if true, this is the key of a local model.If false, this is the key of a global model.
	Iteration int
	Producer string
	CreatedTime string
}

//Iter is a global variable showing the current iteration,we can use UpdateToCurrentIteration function to update Iter to latest iteration.
var Iter int=1

// ModelContract contract for handling writing and reading from the world state
type ModelContract struct {
	contractapi.Contract
}


//CreateLocalModel creates a local model and put it in the ledger
func (mc *ModelContract) CreateLocalModel (ctx contractapi.TransactionContextInterface,producer string,value string) (string,error) {
	UpdateToCurrentIteration(ctx)
	NowTime:=time.Now()
	mn:=ModelKey{true,Iter,producer,NowTime.Format("2006-01-02 15:04:05")}
	ComKey,err:=ctx.GetStub().CreateCompositeKey("ModelKey",[]string{strconv.FormatBool(mn.IsLocalModel),strconv.Itoa(mn.Iteration),mn.Producer,mn.CreatedTime})

	if err!=nil {
		return "",errors.New("cannot create composite key")
	}
	err = ctx.GetStub().PutState(ComKey, []byte(value))

	if err != nil {
		return "",errors.New("unable to interact with world state")
	}
	retstr:=fmt.Sprintf("successfully put a local model to the ledger:制造者%s，第%d代，创造时间为%s",producer,Iter,NowTime.Format("2006-01-02 15:04:05"))
	return retstr,nil
}

//CreateGlobalModel creates a global model and put it in the ledger
func (mc *ModelContract) CreateGlobalModel (ctx contractapi.TransactionContextInterface,producer string,value string) (string,error) {
	UpdateToCurrentIteration(ctx)

	NowTime:=time.Now()
	mn:=ModelKey{false,Iter,producer,NowTime.Format("2006-01-02 15:04:05")}
	ComKey,err:=ctx.GetStub().CreateCompositeKey("ModelKey",[]string{strconv.FormatBool(mn.IsLocalModel),strconv.Itoa(mn.Iteration),mn.Producer,mn.CreatedTime})
	if err!=nil {
		return "",errors.New("cannot create composite key")
	}

	err = ctx.GetStub().PutState(ComKey, []byte(value))

	if err != nil {
		return "",errors.New("unable to interact with world state")
	}

	retstr:=fmt.Sprintf("successfully put a local model to the ledger:制造者%s，第%d代，创造时间为%s",producer,Iter,NowTime.Format("2006-01-02 15:04:05"))

	return retstr,nil
}

//LocalModelSinceLatestGlobalModel gets all new local models since last global model was released
func (mc *ModelContract) LocalModelSinceLatestGlobalModel(ctx contractapi.TransactionContextInterface) ([]string,error) {
	UpdateToCurrentIteration(ctx)
	resultsIterator,err:=ctx.GetStub().GetStateByPartialCompositeKey("ModelKey",[]string{strconv.FormatBool(true),strconv.Itoa(Iter)})

	if err != nil {
		return nil, fmt.Errorf("cannot read local models.Searching failed")
	}
	defer resultsIterator.Close()

	results := []string{}

	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return nil, err
		}

		//datavalueresult:=new(DataValue)//start
		//err=json.Unmarshal(queryResponse.Value,datavalueresult)
		//if err!=nil {
		//	return []string{"反序列化失败"},nil
		//}
		//bytesresult,err:=json.Marshal(datavalueresult)
		//if err!=nil {
		//	return []string{"序列化失败"},nil
		//}//end


		result:="键:"+queryResponse.Key+"值:"+string(queryResponse.Value)

		results = append(results, result)
	}

	return results, nil
}

//QueryAllModel gets all models in the ledger
func (mc *ModelContract) QueryAllModel(ctx contractapi.TransactionContextInterface) ([]string,error) {
	resultsIterator,err:=ctx.GetStub().GetStateByPartialCompositeKey("ModelKey",[]string{})

	if err != nil {
		return nil, fmt.Errorf("cannot read all models.Searching failed")
	}
	defer resultsIterator.Close()

	results := []string{}

	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()

		if err != nil {
			return nil, err
		}

		result:=queryResponse.Key+":"+string(queryResponse.Value)
		results = append(results, result)
	}

	return results, nil
}

//QueryLatestGlobalModel queries latest global model
func (mc *ModelContract) QueryLatestGlobalModel(ctx contractapi.TransactionContextInterface) (string,error) {
	UpdateToCurrentIteration(ctx)
	resultsIterator,err:=ctx.GetStub().GetStateByPartialCompositeKey("ModelKey",[]string{strconv.FormatBool(false),strconv.Itoa(Iter-1)})

	if err != nil {
		return "", fmt.Errorf("cannot read global model.Searching failed")
	}
	defer resultsIterator.Close()

	var result string
	if resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return "", err
		}
		result="键:"+queryResponse.Key+"值:"+string(queryResponse.Value)
	}  else {
		return "无最新全局模型",nil
	}

	return result, nil
}

//QueryLocalModelByIteration reads all local model produced in a certain iteration
func (mc *ModelContract) QueryLocalModelByIteration(ctx contractapi.TransactionContextInterface, iteration string) ([]string, error) {

	resultsIterator,err:=ctx.GetStub().GetStateByPartialCompositeKey("ModelKey",[]string{strconv.FormatBool(true),iteration})

	if err != nil {
		return nil, fmt.Errorf("cannot read %sth iteration localmodel.Search failed",iteration)
	}
	defer resultsIterator.Close()

	results := []string{}

	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()

		if err != nil {
			return nil, err
		}

		result:=queryResponse.Key+":"+string(queryResponse.Value)
		results = append(results, result)
	}

	return results, nil
}

//QueryAverage averages all values in submitted local models since last global model,and return results to terminal
func (mc *ModelContract) QueryAverage (ctx contractapi.TransactionContextInterface) (string,error) {
	UpdateToCurrentIteration(ctx)
	resultsIterator,err:=ctx.GetStub().GetStateByPartialCompositeKey("ModelKey",[]string{strconv.FormatBool(true),strconv.Itoa(Iter)})

	if err != nil {
		return "", fmt.Errorf("cannot read local models.Searching failed")
	}
	defer resultsIterator.Close()
	results:=[]*DataValue{}
	if resultsIterator.HasNext() {
		for resultsIterator.HasNext() {
			queryResponse, err := resultsIterator.Next()
			if err != nil {
				return "", err
			}
			result:=new(DataValue)
			err=json.Unmarshal(queryResponse.Value,result)
			if err!=nil {
				return "failed in unmarshal",err
			}
			results=append(results,result)
		}
	} else {
		return "haven't received new local model since last global model",nil
	}

	AverData:=Average(results)
	AverDataBytes,err:=json.Marshal(AverData)
	if err!=nil {
		return "failed in marshal",err
	}

	return string(AverDataBytes),nil
}

//AverageAndCreateGlobalModel averages all values in local models committed since last global model, and then create a new global model and put it into the ledger
func (mc *ModelContract) AverageAndCreateGlobalModel (ctx contractapi.TransactionContextInterface,producer string) (string,error) {
	UpdateToCurrentIteration(ctx)

	//search for local models
	resultsIterator,err:=ctx.GetStub().GetStateByPartialCompositeKey("ModelKey",[]string{strconv.FormatBool(true),strconv.Itoa(Iter)})
	if err != nil {
		return  "",fmt.Errorf("cannot read local models, Searching failed")
	}
	defer resultsIterator.Close()
	results:=[]*DataValue{}
	if resultsIterator.HasNext() {
		for resultsIterator.HasNext() {
			queryResponse, err := resultsIterator.Next()
			if err != nil {
				return  "",err
			}
			result:=new(DataValue)
			err=json.Unmarshal(queryResponse.Value,result)
			if err!=nil {
				return "",err
			}
			results=append(results,result)
		}
	} else {
		return "",fmt.Errorf("cannot create a new global model for there is no new local model")
	}

	//average
	AverData:=Average(results)
	AverDataBytes,err:=json.Marshal(AverData)
	if err!=nil {
		return "",fmt.Errorf("failed in marshal")
	}

	//create new global model
	NowTime:=time.Now()
	mn:=ModelKey{false,Iter,producer,NowTime.Format("2006-01-02 15:04:05")}
	ComKey,err:=ctx.GetStub().CreateCompositeKey("ModelKey",[]string{strconv.FormatBool(mn.IsLocalModel),strconv.Itoa(mn.Iteration),mn.Producer,mn.CreatedTime})
	if err!=nil {
		return "",errors.New("cannot create composite key")
	}

	err = ctx.GetStub().PutState(ComKey, AverDataBytes)

	if err != nil {
		return "",errors.New("unable to interact with world state")
	}

	retstr:=fmt.Sprintf("successfully put a local model to the ledger:制造者%s，第%d代，创造时间为%s",producer,Iter,NowTime.Format("2006-01-02 15:04:05"))

	return retstr,nil
}



//QueryIteration queries the current iteration
func (mc *ModelContract)QueryIteration (ctx contractapi.TransactionContextInterface) (string,error) {
	defaultIteration:=0

	resultsIterator,err:=ctx.GetStub().GetStateByPartialCompositeKey("ModelKey",[]string{strconv.FormatBool(false)})

	if err != nil {
		return strconv.Itoa(0), fmt.Errorf("cannot read leadger.Searching failed")
	}
	defer resultsIterator.Close()


	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return "0", err
		}
		_,attri,err:=ctx.GetStub().SplitCompositeKey(queryResponse.Key)
		if err != nil {
			return "0", err
		}
		queriedIteration,err:=strconv.Atoi(attri[1])
		if err!=nil {
			return "0",err
		}
		if queriedIteration>defaultIteration {
			defaultIteration=queriedIteration
		}

	}
	defaultIteration+=1

	return strconv.Itoa(defaultIteration), nil

}

//UpdateToCurrentIteration gets the number of current iteration
func UpdateToCurrentIteration (ctx contractapi.TransactionContextInterface) error {
	defaultIteration:=0

	resultsIterator,err:=ctx.GetStub().GetStateByPartialCompositeKey("ModelKey",[]string{strconv.FormatBool(false)})

	if err != nil {
		return  fmt.Errorf("cannot read leadger.Searching failed")
	}
	defer resultsIterator.Close()


	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return  err
		}
		_,attri,err:=ctx.GetStub().SplitCompositeKey(queryResponse.Key)
		if err != nil {
			return  err
		}
		queriedIteration,err:=strconv.Atoi(attri[1])
		if err!=nil {
			return err
		}
		if queriedIteration>defaultIteration {
			defaultIteration=queriedIteration
		}

	}
	defaultIteration+=1
	Iter=defaultIteration
	return  nil

}

func Average(dSet []*DataValue) (res *DataValue) {
	//add
	d := new(DataValue)
	*d = *dSet[0]
	dSetLen := len(dSet)
	for i := 1; i < dSetLen; i++ {
		for k, bia := range dSet[i].Bias {
			d.Bias[k] += bia
		}
		for k, f := range dSet[i].Weight {
			for kk, ff := range f {
				d.Weight[k][kk] += ff
			}
		}
	}
	//average
	for i := range d.Bias {
		d.Bias[i] = d.Bias[i] / float64(dSetLen)
	}
	for i := range d.Weight {
		for i2 := range d.Weight[i] {
			d.Weight[i][i2] /= float64(dSetLen)
		}
	}

	return d
}



//this function cannot play its role. It may due to the function GetStateByRange cannot play its role while using composite key
/*func (sc *ModelContract) ReadAllLocalModel(ctx contractapi.TransactionContextInterface,start string,end string) ([]string, error) {


	resultsIterator,err:=ctx.GetStub().GetStateByRange(start,end)

	if err != nil {
		return nil, fmt.Errorf("Cannot read world state pair for some reason")
	}
	defer resultsIterator.Close()

	results := []string{}

	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()

		if err != nil {
			return nil, err
		}

		result:=queryResponse.Key+string(queryResponse.Value)
		results = append(results, result)
	}

	return results, nil
}*/